package cn.hrfax.stf.sdk.util;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;

import cn.hrfax.stf.sdk.constant.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.NoHttpResponseException;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

/**
 * 为对外发http请求连接可复用作处理
 * 采用PoolingHttpClientConnectionManager来管理client对象
 * @author yongSen.wang
 * @date 2020/6/15 16:24
 */
@Slf4j
public class HttpClientPools {

    private static PoolingHttpClientConnectionManager cm;

    static {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder
            .<ConnectionSocketFactory> create().register("http", plainsf).register("https", sslsf)
            .build();

        cm = new PoolingHttpClientConnectionManager(registry);

        // 将最大连接数增加到200
        cm.setMaxTotal(200);
        // 将每个路由基础的连接增加到200
        cm.setDefaultMaxPerRoute(200);

        //连接池管理
        ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
            new BasicThreadFactory.Builder().namingPattern("httpClient-schedule-pool-%d")
                .daemon(true).build());

        /**
         * 1.使用ScheduledThreadPoolExecutor 代替Timer
         * 2.每1分钟执行一次
         * */
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    //关闭3分钟内不活动的连接
                    cm.closeIdleConnections(3, TimeUnit.MINUTES);
                    log.warn("http pool连接池回收成功,连接池的当前状态:" + cm.getTotalStats());
                } catch (Throwable ex) {
                    log.error("http连接池管理异常", ex);
                }

            }
        }, 30, 60, TimeUnit.SECONDS);

    }

    /**
     * 它管理着连接池，可以同时为很多线程提供http连接请求
     * 如果连接池有有可用的持久连接，连接管理器就会使用其中的一个，而不是再创建一个新的连接。
     * PoolingClientConnectionManager会根据它的配置来分配请求连接,如果连接池中的所有连接都被占用了，那么后续的请求就会被阻塞，直到有连接被释放回连接池中
     *
     * @return
     */
    public static CloseableHttpClient getHttpClient() {

        //请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = new RetryHandler();

        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm)
            .setRetryHandler(httpRequestRetryHandler).build();
        return httpClient;
    }

    /**
     * 构建Get请求方法
     *
     * @param url      请求地址
     * @param paramMap 参数
     * @param charSet  编码
     * @return
     * @throws Exception
     */
    public static HttpGet buildGet(String url, Map<String, String> paramMap,
                                   String charSet) throws Exception {
        HttpGet httpGet = new HttpGet(url);
        RequestConfig requestConfig = getRequestConfig();

        httpGet.setConfig(requestConfig);
        //防止乱码
        httpGet.addHeader("Content-Type", "application/x-www-form-urlencoded;");
        httpGet.addHeader("charset=", charSet);

        List<NameValuePair> params = new ArrayList<NameValuePair>();
        Set<String> keySet = paramMap.keySet();
        for (Iterator<String> it = keySet.iterator(); it.hasNext();) {
            String next = it.next();
            params.add(new BasicNameValuePair(next, paramMap.get(next)));
        }

        String str = EntityUtils.toString(new UrlEncodedFormEntity(params, charSet));

        httpGet.setURI(new URI(url + "?" + str));

        return httpGet;
    }

    /**
     * ConnectTimeout服务器请求超时(15s)
     * ConnectionRequestTimeout 建立请求超时(5s)
     * SocketTimeout 服务器响应超时(30s)
     * @return
     */
    public static RequestConfig getRequestConfig() {
        int httpConnectTimeout = CommonConstant.HTTP_CONNECT_TIMEOUT;

        int httpConnectionRequestTimeout = CommonConstant.HTTP_CONNECTION_REQUEST_TIMEOUT;

        int httpSocketTimeout = CommonConstant.HTTP_SOCKET_TIMEOUT;

        return RequestConfig.custom().setConnectTimeout(httpConnectTimeout)
            .setConnectionRequestTimeout(httpConnectionRequestTimeout)
            .setSocketTimeout(httpSocketTimeout).build();
    }

    /**
     * 构建Post请求方法
     *
     * @param url      请求地址
     * @param paramMap 参数
     * @param charSet  编码
     * @return
     * @throws UnsupportedEncodingException
     */
    public static HttpPost buildPost(String url, Map<String, String> paramMap,
                                     String charSet) throws UnsupportedEncodingException {
        HttpPost httpPost = new HttpPost(url);

        RequestConfig requestConfig = getRequestConfig();

        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-type", "application/x-www-form-urlencoded; charset=" + charSet);
        httpPost.setHeader("Accept", "application/x-www-form-urlencoded");

        List<NameValuePair> valuePairs = new ArrayList<NameValuePair>();
        for (String key : paramMap.keySet()) {
            valuePairs.add(new BasicNameValuePair(key, paramMap.get(key)));
        }

        httpPost.setEntity(new UrlEncodedFormEntity(valuePairs, charSet));
        return httpPost;

    }

    /**
     * 对失败的处理
     */
    static class RetryHandler implements HttpRequestRetryHandler {

        @Override
        public boolean retryRequest(IOException exception, int executionCount,
                                    HttpContext context) {
            if (executionCount >= 3) {// 如果已经重试了3次，就放弃
                return false;
            }
            if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
                return true;
            }
            if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
                return false;
            }
            if (exception instanceof InterruptedIOException) {// 超时
                return false;
            }
            if (exception instanceof UnknownHostException) {// 目标服务器不可达
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
                return false;
            }
            if (exception instanceof SSLException) {// ssl握手异常
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            // 如果请求是幂等的，就再次尝试
            return !(request instanceof HttpEntityEnclosingRequest);
        }
    }

}
